查看原文
其他

爬取必胜客-连享会爬虫系列

Stata连享会 Stata连享会 2020-02-10

作者:游万海 (福州大学)

Stata 连享会: 知乎 | 简书 | 码云

python 爬虫与文本分析专题-现场班
连享会-Python爬虫与文本分析现场班-山西大学 2019.5.17-19

一起学空间计量……

空间计量专题-西安 2019.6.27-30

Stata 与 R:好基友

背景爬虫之工具:R,Stata 和 Python本文要干的事儿一. 网页结构图解分析1. 分析网页结构2. 编码转换二. 正则表达式--零宽断言1. 基本函数2. 零宽断言


连享会 Stata 爬虫和文本分析系列推文:

「Stata: 正则表达式和文本分析」

背景

爬虫之工具:R,Stata 和 Python

统计计量软件众多,例如 RStatapython 等,每个软件都有自己的优点和缺点。

就爬虫而言,pythonR 软件的功能已较为成熟,拥有一系列实现各类功能的包 (package)。比如 R 中的 rvest 包在爬取静态网站数据方面功能很强大,此外,还有 RCurlXML 包等。

相比而言,Stata 虽然在格式化数据处理和计量分析方面非常高效,但在爬虫方面可能还处于 爬行 阶段。曾经有人调侃道:利用 Stata 软件来爬虫的都是比较文艺的 \^-^。

事实并非如此。自 Stata 发布 14.0 版本以来,其字符处理功能已大幅改善,再配合 copycurl 等命令,Stata 的爬虫能力日渐强大。curl (https://curl.haxx.se/) 是一个在命令行下工作的文件传输工具,支持文件的上传和下载,可发送各种 http 请求 给网站,进而抓取网站内容保存到本地。

本文要干的事儿

本文将利用实例来对比 R,Statapython 在爬虫方面的功能。「 100 行代码爬取全国所有必胜客餐厅信息」 一文中,作者利用 python 爬取了全国所有 必胜客餐厅信息 ,包括:餐厅名,地址和电话号码。借鉴该文的分析思路,本文利用 StataR 软件对其结果进行重现,爬取全国必胜客餐厅相关信息,以此说明使用 StataR 爬取数据的步骤和流程。

爬虫的第一步是分析网址,找出其中的规律,以便写循环实现自动爬取;接下来就是把网页信息复制 ( copy ) 下来,保存为 .txt 或其他便于读入统计或计量分析软件的数据文件。

copy 命令是 Stata 中常用的一个爬虫命令,但是在本例中却无法使用。分析必胜客官网网址可以发现,当切换城市时,网址不会随之改变,这时就不能构造有效的地址对其进行循环爬取。为此,我们使用外部命令 curl 命令来实现。

数据的爬取主要包括两部分:第一,从网站上将包含数据信息的源代码下载到本地。这里就涉及到需要对爬取的网站进行详细分析,对于这个网站更加详细的分析,大家可以看之前作者写的推文;第二,利用字符函数或正则表达式对下载的文本文件进行处理,这部分可以详细看之前的一个推文(https://blog.csdn.net/arlionn/article/details/85156842)。

本文分如下部分进行说明:第一,对网页结构进行分析;第二,讲解正则表达式中的零宽断言;第三,对必胜客餐厅分布信息进行抓取;第四,对文章内容进行总结;第五,参考资料说明。

一. 网页结构图解分析

1. 分析网页结构

打开(http://www.pizzahut.com.cn/StoreList) 可以发现下图,当我们切换城市时,相应城市网址不会改变。此时利用 copy 命令,那只能下载其中某个城市一页的数据。

为了进一步了解网站信息,可以利用 google 浏览器,按 F12 会跳出一个页面,再按 F5 刷新 (注意:如果是 win 10 系统,要按 ctrl + R),可以看到如下页面:

我们点击 StoreList,然后出现页面如下,我们可以看到网页的一些 cookie 信息,其中 iplocation 其实就是城市的转码,也就是说,变换这个字段,其实就是切换城市。

2. 编码转换

为了进一步验证,我们利用 Rcurl 包的 curl_escape 函数将字符进行URL转码。例如

  1. library(curl)

  2. x = "福州"

  3. curl_escape(x) ## 输出 %E7%A6%8F%E5%B7%9E

对比上述输出结果与 iplocation 的并不相同,根据开始介绍那篇推文作者,我们可知其实是 福州|0|0 的转码,

  1. x="福州|0|0"

  2. curl_escape(x) ## 输出 %E7%A6%8F%E5%B7%9E%7C0%7C0

当然,这里主题是讲 Stata 软件,我们若想所有程序都在 Stata 环境中运行,那么可以通过 rcall 命令在 Stata 中调用 R 程序。详细的用法可以查看 (https://github.com/haghish/rcall), 这个命令已经在 Stata Journal 2019年第1期正式刊登出来。

  1. github install haghish/rcall /**利用github安装rcall**/

  2. rcall:print("Hello world")

  3. rcall:library(curl)

  4. rcall:x="北京市|0|0"

  5. rcall:print(curl_escape(x))

大家可以发现,这正是 城市|0|0 的 URL 转码恰好是 iplocation 所表示的,通过改变这个字段就可以实现城市的切换。但是,对于同一城市不同页码切换,还需要进一步发现。当我们点击左边的下一页时,可以发现在列表会多一个 Index 选项,点进去发现 From Data 下面有 pageIndexpageSize 两个字段,至此我们知道了如何定义页码,以便进行循环。

二. 正则表达式--零宽断言

1. 基本函数

Stata 14 版本主要的正则表达式函数有:ustrregexmustrregexrfustrregexraustrregexsustr 代表 unicode string

     ustrregexm :  匹配

     ustrregexrf : f 代表 first ,表示只替代第一次出现的匹配字符。

     ustrregexra : a 代表 all ,表示替代全部匹配到的字符。

     ustrregexs :  截取

2. 零宽断言

正则表达式中有许多的匹配规则,这里不在一一说明,请大家花几分钟看 (https://blog.csdn.net/arlionn/article/details/85156842), 这里再给大家介绍一种用法,即 零宽断言,主要有如下四种类型:

     (?=exp): 零宽度正预测先行断言,它断言自身出现位置的后面能匹配表达式 exp

     (?<=exp): 零宽度正回顾后发断言,它断言自身出现位置的前面能匹配表达式 exp

     (?!exp): 零宽度负预测先行断言,断言此位置的后面不能匹配表达式 exp

     (?<!exp) 零宽度负回顾后发断言来断言此位置的前面不能匹配表达式 exp

3. 应用实例

例如,如下数据为 6 位同学的各科成绩,我们想计算每位同学的总成绩,那么需要把数字部分提取出来

  1. clear

  2. input str64 x

  3. "math:96 chinese:85 english:92 physical:90"

  4. "math:91 chinese:82 english:88 physical:98"

  5. "math:86 chinese:85 english:81 physical:90"

  6. "math:93 chinese:85 english:88 physical:90"

  7. "math:70 chinese:85 english:83 physical:91"

  8. "math:80 chinese:85 english:81 physical:92"

  9. end

方法一 :可以利用 moss (Find multiple occurrences of substrings) 命令,代码如下:

  1. moss x, match("([0-9]+|[a-z]+)") regex

  2. list _match*

方法二:利用零宽断言方法进行提取,我们观察到这些数字都是位于 : 符号之后,那么我们想是否可以通过如下提取:

  1. local regex "(?<=\:)([0-9]{2})"

  2. gen grade1 = ustrregexs(1) if ustrregexm(x, "`regex'")

  3. list, clean noobs

可以发现结果并不完整,只取了第一部分的数字。这里需要注意,因为我们只匹配一次(说明:每个软件的规则有点不一样,R 中这样写就可以),下面我们把代码进行稍微修改:

  1. local regex = "(?<=\:)([0-9]{2}).*" * 4

  2. gen grade1 = ustrregexs(1) if ustrregexm(x, "`regex'")

  3. gen grade2 = ustrregexs(2) if ustrregexm(x, "`regex'")

  4. gen grade3 = ustrregexs(3) if ustrregexm(x, "`regex'")

  5. gen grade4 = ustrregexs(4) if ustrregexm(x, "`regex'")

  6. list, clean noobs

匹配结果如下,发现已匹配成功。

  1. list, clean noobs

  2. x grade1 grade2 grade3 grade4

  3. math:96 chinese:85 english:92 physical:90 96 85 92 90

  4. math:91 chinese:82 english:88 physical:98 91 82 88 98

  5. math:86 chinese:85 english:81 physical:90 86 85 81 90

  6. math:93 chinese:85 english:88 physical:90 93 85 88 90

  7. math:70 chinese:85 english:83 physical:91 70 85 83 91

  8. math:80 chinese:85 english:81 physical:92 80 85 81 92

上述代码中 (?<=\:) 表示我们想取出的字符前面需为 : 号,我们再来看下面例子:

  1. clear

  2. input str12 x

  3. "ABAC"

  4. "AAC"

  5. "123AC"

  6. "ABAC"

  7. "1ABAC"

  8. end

  9. gen code=ustrregexs(1) if ustrregexm(x, "(?<=AB)(AC)")

  10. list

上面的例子中,我们想取出 AC,一个条件是该字符前面应该为 AB 。匹配结果为:

  1. list

  2. +--------------+

  3. | x code |

  4. |--------------|

  5. 1. | ABAC AC |

  6. 2. | AAC |

  7. 3. | 123AC |

  8. 4. | ABAC AC |

  9. 5. | 1ABAC AC |

  10. +--------------+

三.必胜客餐厅分布信息爬取

1. 下载 curl

curl 是一个在命令行下工作的文件传输工具,不是 Stata 中的命令,所以不能通过 ssc install 或者findit进行安装。利用 curl 之前需要到其官网下载(https://curl.haxx.se/download.html) ,大家根据自己电脑系统去下载对应的版本,比如我这里下载的是64位的

然后,解压后将 exe 文件放到 C:\Windows\System32,或者放在其他目录,设置好环境变量,就可以接着运行下一步。

2. 数据爬取

(1). 利用 Stata爬取数据

通过对必胜客网站的结构分析,下面就看如何具体的实现,这里主要有两点:第一,要对所有城市进行循环,即利用 curl_escape 所得到的 URL 码,通过对 iplocation 进行循环;第二,对每个城市的不同页码进行循环,这里利用 pageSize 和 pageIndex 进行循环。

  1. clear

  2. import delimited using "D:\我的文档\citylist.csv",clear encoding("GBK") /*第一列为城市名称,第二列为URL码*/

  3. drop in 1

  4. keep in 1/10 /*定义需要爬取的城市*/

  5. rename v1 cityname

  6. rename v2 citycode


  7. levelsof citycode, local(citylevs) /*对城市进行循环*/

  8. di `citylevs'

  9. local nfile 0

  10. foreach f of local citylevs {

  11. local nfile = `nfile' + 1

  12. !curl -H "Cookie: **iplocation** =`f'" ///

  13. --data "pageIndex= 1 &pageSize= 10 " -o "D:/stata15/ado/personal/data/`nfile'.txt" http://www.pizzahut.com.cn/StoreList

  14. *shell `nfile'.txt

  15. }

上述程序爬取了前 10 个城市某一页的数据,利用 levelsof 命令将观察值转换为 local macro,并对 macro 进行循环。这里只爬取了某一页的数据,未对 pageIndex 进行循环。运行上述程序后,可以在 "D:/stata15/ado/personal/data 目录下生成包含数据信息的临时问题,以便后面进行合并。

  1. *对保存的网页数据txt文档进行合并

  2. local files: dir "D:/stata15/ado/personal/data" files "*.txt"

  3. di `"`files'"'


  4. tempfile appendfiles

  5. foreach f of local files {

  6. qui import delimited using "`f'", clear encoding("UTF-8")

  7. qui capture append using `appendfiles'

  8. qui save `appendfiles', replace

  9. }



  10. tempfile temp1 temp2 temp3

  11. use `appendfiles',clear

  12. keep v1


  13. **爬取餐厅名字

  14. gen index1 = ustrregexm(v1,"(re_NameNew)")

  15. gen bsk_name = ustrregexs(1) if ustrregexm(v1,"(?<=>)(.+)(?=<)") & index1==1 /*OK*/

  16. preserve

  17. keep if bsk_name!=""

  18. save `temp1',replace

  19. restore



  20. **爬取餐厅地址和电话号码-第一行地址,第二行电话,递推

  21. gen index2 = ustrregexm(v1,"re_addr")

  22. gen bsk_phone_name= ustrregexs(1) if ustrregexm(v1,"(?<=>)(.+)(?=<)") /*OK*/

  23. keep if index2==1


  24. **电话号码

  25. preserve

  26. keep if mod(_n,2)==0

  27. rename bsk_phone_name bsk_phone

  28. keep bsk_phone

  29. save `temp2',replace

  30. restore


  31. **餐厅地址

  32. preserve

  33. keep if mod(_n,2)==1

  34. rename bsk_phone_name bsk_address

  35. keep bsk_address

  36. save `temp3',replace

  37. restore


  38. **将餐厅名字,电话号码和餐厅地址合并在一个数据集中

  39. use `temp1',clear

  40. drop index1

  41. forv i = 2/3{

  42. qui merge 1:1 _n using `temp`i''

  43. qui keep if _merge==3

  44. drop _merge

  45. }

  46. drop v1

  47. list in 1/10

结果如下:

  1. list in 1/10

  2. +------------------------------------------------------------------------------------------------+

  3. | bsk_name bsk_phone bsk_address |

  4. |------------------------------------------------------------------------------------------------|

  5. 1. | 元洪餐厅 0591-83277835 元洪城商场一层 |

  6. 2. | 台江万达餐厅 0591-87899675 鳌江路8号福州金融街万达广场二层01商铺 |

  7. 3. | 省府餐厅 0591-87571033 八一七北路68号供销大厦二层 |

  8. 4. | 浦上万达餐厅 0591-87933619 金山街道浦上大道272号仓山万达广场A5号一层 |

  9. 5. | 永嘉餐厅 0591-23507836 上街镇永嘉城市广场16号楼永嘉天地2号门一层 |

  10. |------------------------------------------------------------------------------------------------|

  11. 6. | 远洋餐厅 0591-7389205 远洋路423号 |

  12. 7. | 十洋餐厅 0591-28783099 吴航街道郑和中路十洋商务广场(第一期)1-1号铺 |

  13. 8. | 五四HS餐厅 0591-87713937 鼓东街道五四路162号新华福1#、2#连体楼1层08商场和2层02商场 |

  14. 9. | 东二环泰禾餐厅 0591-88510675 岳峰镇竹屿路6号东二环泰禾城市广场三期地下室1层01商业铺位号B13 |

  15. 10. | 杉杉奥莱餐厅 0371-55387121 郑开大道与雁鸣大道交叉口杉杉奥特莱斯购物广场一层A12600 |

  16. +------------------------------------------------------------------------------------------------+

(2). 利用 R 爬取数据

说好的对比呢,说好的 R 程序呢,现在我们来看如何在 R 中实现相同的功能,对比下在爬虫方面的功能:

  1. rm(list=ls())

  2. web="http://www.pizzahut.com.cn/StoreList"

  3. result = readLines(web,encoding="UTF-8")

  4. cities_list = grep('<a href="javascript:addCookie',result,value=T) ##获取城市名字

  5. cities_real = gsub('(.*)(\\(.*\\))(.*)',"\\2",cities_list) ##整理城市名字

  6. regex_cities = noquote(gsub('(\\()(.*)(\\))',"\\2",cities_real)) ##整理城市名字

  7. regex_cities=gsub("('|')","",regex_cities) ##整理城市名字

  8. urlcities = paste(noquote(regex_cities),"|0|0",sep="") ##构造 深圳|0|0 这样的字符

  9. library(curl)

  10. allcookies = curl_escape(urlcities) ##stringrs to URLs

  11. write.csv(cbind(regex_cities,allcookies),file="citylist.csv",row.names=F)

  12. nlen = length(allcookies)

  13. namelist =list()


  14. library(httr)

  15. library(rvest)

  16. library(dplyr)

  17. library(stringr)


  18. k = 1

  19. for (i in 1:2) { ##对全国城市进行循环

  20. varying_cookie <- allcookies[i]

  21. cookie2 <- paste(";iplocation=",varying_cookie,sep="")

  22. cookie = cookie2


  23. headers <- c('cookie'=cookie)


  24. for (j in 1:5){ ##对页数进行循环

  25. payload <-list(pageIndex=j,pageSize=10)

  26. louwill <- GET(web,add_headers(.headers = headers),query = payload) ##GET还是POST

  27. ##message("Getting page ",i)

  28. Sys.sleep(5)

  29. xml <- read_html(louwill$content) %>% html_nodes('.re_RNew')

  30. postdata <- xml %>% html_text()

  31. pdata <- gsub('\r\n'," ",postdata)

  32. pdata=str_trim(pdata)

  33. fdata = strsplit(pdata,split=" ")

  34. bsk_name <- unlist(lapply(fdata,function(x) x[1]))

  35. bsk_address <- unlist(lapply(fdata,function(x) x[2]))

  36. bsk_telephone <- unlist(lapply(fdata,function(x) x[3]))

  37. bsk_region <- rep(regex_cities[i],times=length(bsk_name))

  38. namelist[[k]] = cbind(bsk_region,bsk_name,bsk_address,bsk_telephone)

  39. k = k + 1

  40. }

  41. }

  42. result_data = noquote(do.call(rbind,namelist))

  43. result_data = data.frame(餐厅名字=result_data[,2],地址=result_data[,3],电话号码=result_data[,4])

  44. head(result_data,10)

  45. tail(result_data,10)

部分结果如下

  1. > head(result_data,10)

  2. 餐厅名字 地址

  3. 1 天北路餐厅 天竺镇天北路水木兰亭1号楼底商

  4. 2 甜水园餐厅 万科公园5号楼7号101

  5. 3 慈云寺餐厅 八里庄苏宁广场一层

  6. 4 红桥餐厅 天坛东路46号红桥市场

  7. 5 亦庄力宝餐厅 亦庄荣华中路8号院1号楼一层F1-36及二层F2-26铺位

  8. 6 王府井淘汇餐厅 王府井大街219号-4至9层101的二层2008号商铺

  9. 7 西红门餐厅 欣宁街15号

  10. 8 东坝餐厅 东坝中路金隅嘉品mall二层

  11. 9 宋家庄餐厅 宋家庄路26号院11号楼1层114/115-2

  12. 10 理想城餐厅 西红门镇宏福路洪坤广场1层

  13. 电话号码

  14. 1 010-50933941

  15. 2 010-5535431

  16. 3 010-85725206

  17. 4 010-67111853

  18. 5 010-52594995

  19. 6 010-65228561

  20. 7 010-60221990

  21. 8 010-85095069

  22. 9 010-67904805

  23. 10 010-51077619

  24. > tail(result_data,10)

  25. 餐厅名字 地址 电话号码

  26. 91 福泉路HS餐厅 福泉北路511-18A号一层 021-52650093

  27. 92 海悦餐厅 曲阜西路398号 021-61235119

  28. 93 星游城新HS餐厅 天钥桥路580号 021-61619851

  29. 94 凯旋南路HS餐厅 龙吴路51弄1号楼一层101室 021-54610675

  30. 95 黄兴宅急送餐厅 国科路80号一层80-1、80-2单元 021-31194881

  31. 96 海上海宅急送餐厅 飞虹路568弄46号一、二层 021-35305602

  32. 97 威宁路餐厅 威宁路55号 021-52395265

  33. 98 团结餐厅 团结路6号居民生活中心二楼 021-56041252

  34. 99 龙胜路餐厅 龙胜路404部分及406号一层 021-57870156

  35. 100 博兴餐厅 凌河路757-759号一层 021-50264386

四.总结

本文主要阐述如何利用 Stata 爬取网页数据,curl 作为 copy 命令的一个补充,使 Stata 在数据爬取方面进一步完善,特别是Stata 14 以后,字符函数有了很大的改进。通过阅读本文,可以掌握如下信息:

1. 了解 Stata 爬取数据的一般流程和套路,掌握 curl 的用法。

2. 了解正则表达式中零宽断言的用法。

3. 掌握 Stata 中调用R的方法,利用 rcall 函数可以实现交互模式。

五.参考资料

1. 正则表达式在线测试网站(https://regex101.com).

2. curl 网站(https://curl.haxx.se/)

3. 极客猴:100 行代码爬取全国所有必胜客餐厅信息(https://blog.csdn.net/zhusongziye/article/details/84310490).

4. RStata (https://fsolt.org/blog/2018/08/15/switch-to-r.html)


关于我们

  • 【Stata 连享会(公众号:StataChina)】由中山大学连玉君老师团队创办,旨在定期与大家分享 Stata 应用的各种经验和技巧。

  • 公众号推文同步发布于 CSDN-Stata连享会 、简书-Stata连享会 和 知乎-连玉君Stata专栏。可以在上述网站中搜索关键词StataStata连享会后关注我们。

  • 点击推文底部【阅读原文】可以查看推文中的链接并下载相关资料。

  • Stata连享会 精彩推文1  || 精彩推文2

联系我们

  • 欢迎赐稿: 欢迎将您的文章或笔记投稿至Stata连享会(公众号: StataChina),我们会保留您的署名;录用稿件达五篇以上,即可免费获得 Stata 现场培训 (初级或高级选其一) 资格。

  • 意见和资料: 欢迎您的宝贵意见,您也可以来信索取推文中提及的程序和数据。

  • 招募英才: 欢迎加入我们的团队,一起学习 Stata。合作编辑或撰写稿件五篇以上,即可免费获得 Stata 现场培训 (初级或高级选其一) 资格。

  • 联系邮件: StataChina@163.com

往期精彩推文


python 爬虫与文本分析
Python爬虫与文本分析专题-海报

一起学空间计量……空间计量专题研讨班-连享会(2019年6月)

空间计量专题-西安

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存